package com.study.crypto.utils;

import com.study.crypto.utils.bean.KeyValue;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.util.encoders.Hex;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.KeyPair;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Songjin
 * @since 2020-12-30 18:52
 */
public final class CertUtils {
    
    private CertUtils() {
    }
    
    /**
     * 组装 dn
     * @param countryName       证书C项
     * @param organizationName  证书O项
     * @param commonName        证书CN项
     * @return dn
     */
    public static X500Name buildDistinctName(String countryName, String organizationName, String commonName) {
        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
        builder.addRDN(BCStyle.C, countryName);
        builder.addRDN(BCStyle.O, organizationName);
        builder.addRDN(BCStyle.CN, commonName);
        return builder.build();
    }

    /**
     * 将证书 dn 字符串转换成 X500Name
     * @param dn 将证书 dn 字符串
     * @return X500Name
     */
    public static X500Name buildDistinctName(String dn) {
        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
        List<KeyValue<ASN1ObjectIdentifier, String>> dnMap = CertUtils.splitDn(dn);
        for (KeyValue<ASN1ObjectIdentifier, String> kv : dnMap) {
            builder.addRDN(kv.getKey(), kv.getValue());
        }
        return builder.build();
    }

    /**
     * 将证书 DN 转换成 {@code Map<oid,String>} 映射
     * @param dn DN字符串
     * @return {@code Map<oid,String>}映射
     */
    public static List<KeyValue<ASN1ObjectIdentifier, String>> splitDn(String dn) {
        // 先删除字符串中的空白字符
        String dnTrim = StringUtils.deleteWhitespace(dn);
        return Arrays.stream(dnTrim.split(","))
                     .map(textItem -> textItem.trim().split("="))
                     .map(itemSplit -> {
                         KeyValue<ASN1ObjectIdentifier, String> keyValue = new KeyValue<>();
                         keyValue.setKey(BCStyle.INSTANCE.attrNameToOID(itemSplit[0]));
                         // 存在dn子项值为空的情况
                         if (itemSplit.length < 2) {
                             keyValue.setValue("");
                         } else {
                             keyValue.setValue(itemSplit[1]);
                         }
                         return keyValue;
                     }).collect(Collectors.toList());
    }
    
    /**
     * 签发终端用户证书
     * @param publicKeyB64  公钥base64
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return Certificate
     * @throws Exception 异常
     */
    public static Certificate generateUserCertificate(String publicKeyB64,
                                                      X500Name subject,
                                                      ContentSigner signer,
                                                      Certificate issuer,
                                                      LocalDateTime notBefore,
                                                      LocalDateTime notAfter,
                                                      BigInteger serialNumber) throws Exception {
        return issueCertificate(true, publicKeyB64, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }

    /**
     * 签发终端用户证书
     * @param publicKeyInfo 公钥信息
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return Certificate
     * @throws Exception 异常
     */
    public static Certificate generateUserCertificate(SubjectPublicKeyInfo publicKeyInfo,
                                                      X500Name subject,
                                                      ContentSigner signer,
                                                      Certificate issuer,
                                                      LocalDateTime notBefore,
                                                      LocalDateTime notAfter,
                                                      BigInteger serialNumber) throws Exception {
        return issueCertificate(publicKeyInfo, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }

    /**
     * 签发终端用户加密证书
     * @param publicKeyB64  公钥base64
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return Certificate
     * @throws Exception 异常
     */
    public static Certificate generateUserEncCertificate(String publicKeyB64,
                                                         X500Name subject,
                                                         ContentSigner signer,
                                                         Certificate issuer,
                                                         LocalDateTime notBefore,
                                                         LocalDateTime notAfter,
                                                         BigInteger serialNumber) throws Exception {
        return issueCertificate(false, publicKeyB64, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }
    
    /**
     * 签发终端用户加密证书
     * @param keyPair       公私钥对
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return Certificate
     * @throws Exception 异常
     */
    public static Certificate generateUserEncCertificate(KeyPair keyPair,
                                                         X500Name subject,
                                                         ContentSigner signer,
                                                         Certificate issuer,
                                                         LocalDateTime notBefore,
                                                         LocalDateTime notAfter,
                                                         BigInteger serialNumber) throws Exception {
        BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
        byte[] publicKeyBytes = publicKey.getQ().getEncoded(false);
        String publicKeyB64 = Base64.getEncoder().encodeToString(publicKeyBytes);
        return issueCertificate(false, publicKeyB64, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }

    private static Certificate issueCertificate(SubjectPublicKeyInfo publicKeyInfo,
                                                X500Name subject,
                                                ContentSigner signer,
                                                Certificate issuer,
                                                LocalDateTime notBefore,
                                                LocalDateTime notAfter,
                                                BigInteger serialNumber) throws Exception {
        return issueCertificatePublicKeyInfo(true, publicKeyInfo, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }

    public static Certificate issueCertificate(boolean signOrEnc,
                                                String publicKeyB64,
                                                X500Name subject,
                                                ContentSigner signer,
                                                Certificate issuer,
                                                LocalDateTime notBefore,
                                                LocalDateTime notAfter,
                                                BigInteger serialNumber) throws Exception {
        ASN1ObjectIdentifier oid = signer.getAlgorithmIdentifier().getAlgorithm();
        SubjectPublicKeyInfo subjectKeyInfo = KeyUtils.obtainSubjectPublicKeyInfoBase64(oid, publicKeyB64);
        return issueCertificatePublicKeyInfo(signOrEnc, subjectKeyInfo, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }

    private static Certificate issueCertificatePublicKeyInfo(boolean signOrEnc,
                                                             SubjectPublicKeyInfo publicKeyInfo,
                                                             X500Name subject,
                                                             ContentSigner signer,
                                                             Certificate issuer,
                                                             LocalDateTime notBefore,
                                                             LocalDateTime notAfter,
                                                             BigInteger serialNumber) throws Exception {
        Date notBefore_ = EssPdfUtil.of(notBefore);
        Date notAfter_ = EssPdfUtil.of(notAfter);
        BcX509ExtensionUtils extUtils = new BcX509ExtensionUtils();
        X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuer.getSubject(), serialNumber, notBefore_, notAfter_, subject, publicKeyInfo);
        builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
        int usage;
        if (signOrEnc) {
            usage = KeyUsage.digitalSignature | KeyUsage.nonRepudiation;
        } else {
            usage = KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement;
        }
        builder.addExtension(Extension.keyUsage, true, new KeyUsage(usage));
        // 授权密钥标识
        AuthorityKeyIdentifier authKeyId = extUtils.createAuthorityKeyIdentifier(issuer.getSubjectPublicKeyInfo());
        builder.addExtension(Extension.authorityKeyIdentifier, false, authKeyId);
        SubjectKeyIdentifier subjectKeyIdentifier = extUtils.createSubjectKeyIdentifier(publicKeyInfo);
        // 使用者密钥标识
        builder.addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);
        X509CertificateHolder certificateHolder = builder.build(signer);
        return certificateHolder.toASN1Structure();
    }

    /**
     * 判断证书是否是签名证书
     * @param certBytes 证书字节数据
     * @return 布尔值
     * @throws CertificateException 异常
     * @throws NoSuchProviderException 异常
     */
    public static boolean usageDigitalSignature(byte[] certBytes) throws CertificateException, NoSuchProviderException {
        CertificateFactory instance = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        X509Certificate certificate = (X509Certificate) instance.generateCertificate(new ByteArrayInputStream(certBytes));
        boolean[] keyUsage = certificate.getKeyUsage();
        return keyUsage[0];
    }
    
    /**
     * 从字节数组构建证书对象
     * @deprecated 废弃，使用 {@link #convertCertificate(byte[])} 替代
     * @param certBytes 证书字节数组
     * @return Certificate
     * @throws IOException 异常
     */
    @Deprecated
    public static Certificate rebuildCertificate(byte[] certBytes) throws IOException {
        X509CertificateHolder holder = new X509CertificateHolder(certBytes);
        return holder.toASN1Structure();
    }

    /**
     * 从证书base64字符串构建证书对象
     * @param certB64 证书base64字符串
     * @return Certificate
     * @throws IOException 异常
     */
    public static Certificate convertCertificate(String certB64) throws IOException {
        byte[] certBytes = Base64.getDecoder().decode(certB64);
        X509CertificateHolder holder = new X509CertificateHolder(certBytes);
        return holder.toASN1Structure();
    }

    /**
     * 从字节数组构建证书对象
     * @param certBytes 证书字节数组
     * @return Certificate
     * @throws IOException 异常
     */
    public static Certificate convertCertificate(byte[] certBytes) throws IOException {
        X509CertificateHolder holder = new X509CertificateHolder(certBytes);
        return holder.toASN1Structure();
    }

    /**
     * 从字节数组构建证书对象
     * @param certBytes 证书字节数组
     * @return X509Certificate
     * @throws CertificateException 异常
     * @throws NoSuchProviderException 异常
     */
    public static X509Certificate rebuildX509Certificate(byte[] certBytes) throws CertificateException, NoSuchProviderException {
        CertificateFactory instance = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        return (X509Certificate) instance.generateCertificate(new ByteArrayInputStream(certBytes));
    }
    
    /**
     * 从证书中解析 KeyUsage 整数值
     * @param certBytes 证书字节数组
     * @return KeyUsage 整数值
     * @throws IOException 异常
     */
    public static int certificateUsage(byte[] certBytes) throws IOException {
        Certificate certificate = convertCertificate(certBytes);
        Extensions  extension   = certificate.getTBSCertificate().getExtensions();
        KeyUsage keyUsage = KeyUsage.fromExtensions(extension);
        byte[] byteArr = new byte[4];
        Arrays.fill(byteArr, (byte) 0);
        byte[] usageBytes = keyUsage.getBytes();
        System.arraycopy(usageBytes, 0, byteArr, 0, usageBytes.length);
        ByteBuffer byteBuf = ByteBuffer.allocate(4);
        byteBuf.put(byteArr);
        byteBuf.order(ByteOrder.LITTLE_ENDIAN);
        return byteBuf.getInt(0);
    }

    /**
     * 获取证书授权密钥标识符
     * @param certificate 证书
     * @return 授权密钥标识符
     */
    public static String authorityKeyIdentifier(Certificate certificate) {
        Extension extension = certificate.getTBSCertificate().getExtensions()
                                         .getExtension(Extension.authorityKeyIdentifier);
        ASN1Sequence parsedValue = (ASN1Sequence) extension.getParsedValue();
        ASN1TaggedObject objectAt = (ASN1TaggedObject) (parsedValue).getObjectAt(0);
        byte[] octets = ((ASN1OctetString) objectAt.getBaseObject()).getOctets();
        return Hex.toHexString(octets);
    }

    /**
     * 获取证书使用者密钥标识符
     * @param certificate 证书
     * @return 使用者密钥标识符
     */
    public static String subjectKeyIdentifier(Certificate certificate) {
        Extension extension = certificate.getTBSCertificate().getExtensions()
                                         .getExtension(Extension.subjectKeyIdentifier);
        ASN1OctetString parsedValue = (ASN1OctetString) extension.getParsedValue();
        return Hex.toHexString(parsedValue.getOctets());
    }

    /**
     * 判断证书是否有usages用途。可用于判断证书是否是签名证书、加密证书
     *
     * @param certB64 证书base64字符串
     * @param usages  按位与的用途集合
     * @return 是否具备
     * @throws IOException 异常
     */
    public static boolean hasUsages(String certB64, int usages) throws IOException {
        byte[] certBytes = Base64.getDecoder().decode(certB64);
        return hasUsages(certBytes, usages);
    }

    /**
     * 判断证书是否有usages用途。可用于判断证书是否是签名证书、加密证书
     *
     * @param certBytes 证书字节数组
     * @param usages    按位与的用途集合
     * @return 是否具备
     * @throws IOException 异常
     */
    public static boolean hasUsages(byte[] certBytes, int usages) throws IOException {
        Certificate certificate = convertCertificate(certBytes);
        Extensions extensions = certificate.getTBSCertificate().getExtensions();
        KeyUsage keyUsage = KeyUsage.fromExtensions(extensions);
        return keyUsage.hasUsages(usages);
    }
}